David Montero Loaiza
Contenido a entregar: Responde a las preguntas planteadas y aporta el código en R utilizado para responderlas. Se debe enviar un único documento con texto y código entrelazado, bien sea en formato pdf o html.
Fecha de entrega: 7 Abril 2019
require(gRain)
Loading required package: gRain
package 㤼㸱gRain㤼㸲 was built under R version 3.6.3Loading required package: gRbase
package 㤼㸱gRbase㤼㸲 was built under R version 3.6.3
Attaching package: 㤼㸱gRbase㤼㸲
The following objects are masked from 㤼㸱package:bnlearn㤼㸲:
ancestors, children, parents
Introducción a la práctica
Utilizaremos un conjunto de datos diarios de ocurrencia de meteoros en el aeropuerto de Parayas (Santander), considerando una serie de variables meteorológicas registradas diariamente durante un periodo aproximado de 10 años (3286 registros), en concreto: precipitación, nieve, granizo, tormenta, niebla, rocío, escarcha, nieve en el suelo, neblina y racha máxima de viento superior a 50 km/h. Los datos se encuentran guardados en el fichero de texto meteoro.txt. Las variables se encuentran discretizadas de forma binaria (ocurrencia/ausencia).
meteoro <- read.table('meteoro.txt',header = TRUE)
En este caso, y a través del asesoramiento de un meteorólogo, se ha elaborado el siguiente diagrama acíclico dirigido (DAG), que recoge las relaciones de dependencia entre las variables:
Ejercicio 1 (2.5 Puntos)
A partir del grafo dado por el experto, determina la expresión que refleja la factorización de la distribución global, y escríbela como una secuencia de caracteres del tipo generado por la función modelstring del paquete de R bnlearn.
expresion = "[viento][lluvia|viento][rocio|viento][tormenta|lluvia][niebla|rocio:viento][escarcha|rocio][nieve|tormenta][granizo|tormenta][neblina|niebla][nieveSuelo|nieve]"
NOTA: Como ya se comentado anteriormente, es importante evitar tildes a la hora de nombrar los nodos. Tampoco son recomendables espacios en blanco ni ningún tipo de carácter especial.
A continuación, introduce el DAG en R utilizando la definicíon del modelo que acabas de crear
dag = model2network(expresion)
plot(dag)

Realiza una lista de los padres e hijos de cada uno de los nodos
parentsDAG = lapply(X = names(meteoro),FUN = function(x) parents(dag,x))
childrenDAG = lapply(X = names(meteoro),FUN = function(x) children(dag,x))
names(parentsDAG) = names(meteoro)
names(childrenDAG) = names(meteoro)
parentsDAG
$lluvia
[1] "viento"
$nieve
[1] "tormenta"
$granizo
[1] "tormenta"
$tormenta
[1] "lluvia"
$niebla
[1] "rocio" "viento"
$rocio
[1] "viento"
$escarcha
[1] "rocio"
$nieveSuelo
[1] "nieve"
$neblina
[1] "niebla"
$viento
character(0)
childrenDAG
$lluvia
[1] "tormenta"
$nieve
[1] "nieveSuelo"
$granizo
character(0)
$tormenta
[1] "granizo" "nieve"
$niebla
[1] "neblina"
$rocio
[1] "escarcha" "niebla"
$escarcha
character(0)
$nieveSuelo
character(0)
$neblina
character(0)
$viento
[1] "lluvia" "niebla" "rocio"
Realiza una lista de todas las conexiones fundamentales presentes en el grafo, y realiza una clasificación de cada una de ellas determinando si es una estructura en serie, divergente o convergente.
¿Se observa alguna v-estructura en el grafo?
vstructs(dag)
X Z Y
Intenta introducir un arco que parta del nodo neblina y se dirija hacia escarcha, y otro que vaya de granizo a lluvia. Comenta qué sucede en cada caso, y si el resultado daría lugar a una red bayesiana válida.
dag = set.arc(dag,from = "neblina",to = "escarcha")
#dag = set.arc(dag,from = "granizo",to = "lluvia")
De neblina a escarcha se puede introducir un arco, mientras que de granizo a lluvia no porque el grafo debe ser acíclico.
Determina la manta de Markov del nodo rocio.
mb(dag,"rocio")
[1] "escarcha" "neblina" "niebla" "viento"
Neblina entra en la manta de Markov ya que ahora comparte el hijo Escarcha con Rocio.
Introduce un arco que parta del nodo Lluvia y se dirija al nodo Niebla, ¿cambia la manta de Markov del nodo rocio? En caso afirmativo, ¿cómo lo hace?
dag = set.arc(dag,from = "lluvia",to = "niebla")
mb(dag,"rocio")
[1] "escarcha" "lluvia" "neblina" "niebla" "viento"
Lluvia se introduce en la manta de Markov ya que comparte un hijo (Niebla) con Rocio.
Para continuar, elimina el arco definido anteriormente entre los nodos Lluvia y Niebla, recuperando el DAG original.
dag = drop.arc(dag,from = "lluvia",to = "niebla")
dag = drop.arc(dag,from = "neblina",to = "escarcha")
Ejercicio 2 (2.5 Puntos)
Considerando la información proporcionada por el conjunto de datos meteor, construye la red bayesiana utilizando el método bayesiano de estimación paramétrica.
bn = bn.fit(dag, data = meteoro, method = "bayes")
¿Cuál sería el número potencial de parámetros (sin usar la red bayesiana) del modelo para calcular la probabilidad global si no utilizasemos el DAG?
2^(ncol(meteoro))
[1] 1024
¿Cuántos parámetros tiene la distribución global dada por la red bayesiana?
nparams(bn)
[1] 21
Determina el número de parámetros asociado a cada una de las distribuciones locales
Obtén las tablas de probabilidad condicional asociadas los nodos granizo y niebla. Ahora representa la información de cada tabla en sendos gráficos.
bn$granizo
Parameters of node granizo (multinomial distribution)
Conditional probability table:
tormenta
granizo n s
n 0.990183352 0.745742092
s 0.009816648 0.254257908
bn.fit.barchart(bn$granizo, main = "Granizo", xlab = "Pr(granizo|tormenta)", ylab = "Estado de Granizo")

bn$niebla
Parameters of node niebla (multinomial distribution)
Conditional probability table:
, , viento = n
rocio
niebla n s
n 0.946399802 0.855234203
s 0.053600198 0.144765797
, , viento = s
rocio
niebla n s
n 0.995195025 0.934108527
s 0.004804975 0.065891473
bn.fit.barchart(bn$niebla, main = "Niebla", xlab = "Pr(niebla|viento,rocio)", ylab = "Estado de Niebla")

Ejercicio 3 (2.5 Puntos)
Una vez construída la red hemos establecido la base de conocimiento del sistema inteligente. A continuación se puede calcular la probabilidad de cualquier variable o conjunto de variables condicionadas a cualquier evidencia que se tenga disponible para un problema dado, es decir, realizar la inferencia.
Observa la estructura del DAG y repasa la teoría y el concepto de d-separación. Responde razonadamente si las siguientes afirmaciones son verdaderas o falsas, utilizando únicamente la estructura del DAG y el concepto de d-separación:
La nieve y el granizo son fenómenos independientes a priori: Falso
Condición: Vacío Nieve <- Tormenta -> Granizo: Nieve y granizo no están d-separados ya que no hay v-estructuras y Tormenta no tiene arcos convergentes pero no está en el conjunto condición (conjunto vacío).
dsep(bn,"nieve","granizo")
[1] FALSE
La nieve y el granizo son fenómenos independientes dado que haya habido tormenta: Verdadero
Condición: Tormenta Nieve <- Tormenta -> Granizo: Nieve y granizo están d-separados ya que Tormenta no tiene arcos convergentes y está en el conjunto condición (conjunto Tormenta).
dsep(bn,"nieve","granizo","tormenta")
[1] TRUE
La nieve en el suelo y la neblina son fenómenos independientes: Falso
Condición: Vacío
NieveSuelo <- Nieve <- Tormenta <- Lluvia <- Viento -> Niebla -> Neblina: NieveSuelo y granizo no están d-separados ya que no hay v-estructuras y el nodo Viento no tiene arcos convergentes pero no está en el conjunto condición (conjunto vacío).
NieveSuelo <- Nieve <- Tormenta <- Lluvia <- Viento -> Rocio -> Niebla -> Neblina: NieveSuelo y granizo no están d-separados ya que no hay v-estructuras y el nodo Viento no tiene arcos convergentes pero no está en el conjunto condición (conjunto vacío).
dsep(bn,"nieveSuelo","neblina")
[1] FALSE
La nieve en el suelo y la neblina son fenómenos independientes dado que haya habido tormenta: Verdadero
Condición: Tormenta
NieveSuelo <- Nieve <- Tormenta <- Lluvia <- Viento -> Niebla -> Neblina: NieveSuelo y granizo están d-separados ya que no hay v-estructuras y el nodo Viento no tiene arcos convergentes pero no está en el conjunto condición (conjunto Tormenta).
NieveSuelo <- Nieve <- Tormenta <- Lluvia <- Viento -> Rocio -> Niebla -> Neblina: NieveSuelo y granizo están d-separados ya que no hay v-estructuras y el nodo Viento no tiene arcos convergentes pero no está en el conjunto condición (conjunto Tormenta).
dsep(bn,"nieveSuelo","neblina","tormenta")
[1] TRUE
NOTA: En este apartado se deberá aplicar la inferencia exacta. Conocido que en un día dado se han producido tormentas, calcula cómo afecta este hecho a la probabilidad de que se produzcan los siguientes fenómenos meteorológicos:
junction = compile(as.grain(bn))
tormenta.s = setEvidence(junction,nodes = "tormenta", states = "s")
Que llueva, P(lluvia=s|tormenta=s)
querygrain(tormenta.s, nodes = "lluvia")$lluvia["s"]
s
0.959854
Que haya rachas de viento superiores a 50 Km/h, P(viento=s|tormenta=s)
querygrain(tormenta.s, nodes = "viento")$viento["s"]
s
0.2168608
Que llueva y que además las rachas de viento superen los 50 Km/h, P(lluvia=s,viento=s|tormenta=s) [en el enunciado original aparecía viento=n, dado el enunciado lo cambié por viento=s]
querygrain(tormenta.s, nodes = c("lluvia","viento"),type = "joint")["s","s"]
[1] 0.2150672
A partir de la información revelada por la red bayesiana, sabiendo que un día se producen tormentas:
¿Hay mayor probabilidad de que llueva cuando se producen tormentas que cualquier otro día?
tormenta.n = setEvidence(junction,nodes = "tormenta", states = "n")
querygrain(tormenta.n, nodes = "lluvia")$lluvia["s"]
s
0.5287198
Sí, hay mayor probabilidad de que llueva cuando se producen tormentas (0.91) que cuando no (0.52).
¿Aumenta o disminuye la probabilidad de tener rachas de viento mayores de 50 Km/h cuando se produce tormenta? ¿Cuánto?
querygrain(tormenta.n, nodes = "viento")$viento["s"]
s
0.1395214
Aumenta la probabilidad de 0.13 a 0.21 de tener rachas de viento mayores de 50 Km/h cuando se produce una tormenta.
Dado el ejercicio anterior, repetir ahora el ejercicio mediante inferencia aproximada, calculando para cada una de las estimaciones 100 realizaciones. Para cada una de las respuestas anteriores, representa un diagrama de cajas que represente la dispersión en la estimación de la probabilidad, marcando además el valor obtenido de manera exacta en el apartado anterior:
Que llueva, P(lluvia=s|tormenta=s)
inf.exacta = querygrain(tormenta.s, nodes = "lluvia")$lluvia["s"]
inf.aprox = NULL
for(i in 1:100) inf.aprox = c(inf.aprox,cpquery(bn,event = (lluvia == "s"),evidence = (tormenta == "s")))
boxplot(inf.aprox,main = "(P(lluvia=s|tormenta=s))")
abline(h = inf.exacta,col = "red")
legend("topleft",lty = 1,col = "red",legend = "Inferencia Exacta")

Que haya rachas de viento superiores a 50 Km/h, P(viento=s|tormenta=s)
inf.exacta = querygrain(tormenta.s, nodes = "viento")$viento["s"]
inf.aprox = NULL
for(i in 1:100) inf.aprox = c(inf.aprox,cpquery(bn,event = (viento == "s"),evidence = (tormenta == "s")))
boxplot(inf.aprox,main = "(P(viento=s|tormenta=s))")
abline(h = inf.exacta,col = "red")
legend("topleft",lty = 1,col = "red",legend = "Inferencia Exacta")

Que llueva y que además las rachas de viento superen los 50 Km/h, P(lluvia=s,viento=s|tormenta=s) [en el enunciado original aparecía viento=n, dado el enunciado lo cambié por viento=s]
inf.exacta = querygrain(tormenta.s, nodes = c("lluvia","viento"),type = "joint")["s","s"]
inf.aprox = NULL
for(i in 1:100) inf.aprox = c(inf.aprox,cpquery(bn,event = (lluvia == "s") & (viento == "s"),evidence = (tormenta == "s")))
boxplot(inf.aprox,main = "(P(lluvia=s,viento=s|tormenta=s))")
abline(h = inf.exacta,col = "red")
legend("topleft",lty = 1,col = "red",legend = "Inferencia Exacta")

¿Hay mayor probabilidad de que llueva cuando se producen tormentas que cualquier otro día?
inf.exacta.n = querygrain(tormenta.n, nodes = "lluvia")$lluvia["s"]
inf.exacta.s = querygrain(tormenta.s, nodes = "lluvia")$lluvia["s"]
inf.aprox.n = NULL
inf.aprox.s = NULL
for(i in 1:100){
inf.aprox.s = c(inf.aprox.s,cpquery(bn,event = (lluvia == "s"),evidence = (tormenta == "s")))
inf.aprox.n = c(inf.aprox.n,cpquery(bn,event = (lluvia == "s"),evidence = (tormenta == "n")))
}
par(mfrow = c(1,2))
boxplot(inf.aprox.n,main = "(P(lluvia=s|tormenta=n))",ylim = c(0,1))
abline(h = inf.exacta.n,col = "red")
legend("topleft",lty = 1,col = "red",legend = "Inferencia Exacta")
boxplot(inf.aprox.s,main = "(P(lluvia=s|tormenta=s))",ylim = c(0,1))
abline(h = inf.exacta.s,col = "blue")
legend("bottomleft",lty = 1,col = "blue",legend = "Inferencia Exacta")

Sí existe una mayor probabilidad de que llueva cuando hay tormentas.
¿Aumenta o disminuye la probabilidad de tener rachas de viento mayores de 50 Km/h cuando se produce tormenta? ¿Cuánto?
inf.exacta.n = querygrain(tormenta.n, nodes = "viento")$viento["s"]
inf.exacta.s = querygrain(tormenta.s, nodes = "viento")$viento["s"]
inf.aprox.n = NULL
inf.aprox.s = NULL
for(i in 1:100){
inf.aprox.s = c(inf.aprox.s,cpquery(bn,event = (viento == "s"),evidence = (tormenta == "s")))
inf.aprox.n = c(inf.aprox.n,cpquery(bn,event = (viento == "s"),evidence = (tormenta == "n")))
}
par(mfrow = c(1,2))
boxplot(inf.aprox.n,main = "(P(viento=s|tormenta=n))",ylim = c(0,1))
abline(h = inf.exacta.n,col = "red")
legend("topleft",lty = 1,col = "red",legend = "Inferencia Exacta")
boxplot(inf.aprox.s,main = "(P(viento=s|tormenta=s))",ylim = c(0,1))
abline(h = inf.exacta.s,col = "blue")
legend("topleft",lty = 1,col = "blue",legend = "Inferencia Exacta")

Aumenta la probabilidad, sin embargo, para inferencias aproximadas, es necesario realizar varias pruebas para acercarnos más a un valor estimado.
Ejercicio 4 (2.5 Puntos)
Como hemos visto, es posible realizar un aprendizaje automático de la estructura del DAG a partir de los datos, usando algoritmos específicos para ello. Durante las clases hemos visto el ejemplo del algoritmo hill-climbing, aunque como vimos, hay otras posibilidades. Tambien hemos visto que podemos combinar nuestra experiencia y el aprendizaje automático definiendo previamente relaciones entre variables que queremos introducir o descartar en el DAG resultante. Además, se ha explicado que existen scores que sirven como criterio para evaluar la fuerza de la dependendencia entre nodos de la red y comparar la bondad de ajuste del modelo.
Evalúa la significación de los arcos dibujados por el experto en el actual DAG utilizando el estadístico χ2. ¿Hay algún arco no significativo?
strength = arc.strength(dag, data = meteoro, criterion = "x2")
strength
from to strength
1 viento lluvia 5.887107e-48
2 viento rocio 1.730098e-23
3 lluvia tormenta 1.707081e-33
4 rocio niebla 2.087399e-17
5 viento niebla 1.591451e-05
6 rocio escarcha 2.106559e-05
7 tormenta nieve 1.051550e-13
8 tormenta granizo 3.189229e-104
9 niebla neblina 1.300969e-27
10 nieve nieveSuelo 2.515328e-101
No hay arcos que no sean significativos a un nivel de confianza del 95%.
¿Cuáles son los tres pares de nodos que presentan un arco de unión más fuerte?
head(strength[order(strength$strength),],3)
from to strength
8 tormenta granizo 3.189229e-104
10 nieve nieveSuelo 2.515328e-101
1 viento lluvia 5.887107e-48
Además del algoritmo hill-climbing, existe otro algoritmo popular de tipo “voraz” denominado Tabu search. En bnlearn se encuentra implementado a traves de la función tabu, y los argumentos de entrada son similares a los vistos para hill-climbing.
Compara el score global (BIC) obtenido por el DAG inicial, con los obtenidos por los DAG aprendidos de forma automática con los algoritmos tabu y hill-climbing. ¿Cuál obtiene mejor score?
bic.dag.inicial = score(dag,data = meteoro)
dag.hc = model2network(modelstring(hc(meteoro)))
dag.tabu = model2network(modelstring(tabu(meteoro)))
bic.dag.hc = score(dag.hc,data = meteoro)
bic.dag.tabu = score(dag.tabu,data = meteoro)
bics = c(bic.dag.inicial,bic.dag.hc,bic.dag.tabu)
names(bics) = c("Inicial","HC","Tabu")
bics
Inicial HC Tabu
-9724.872 -9366.750 -9365.175
El método de Tabu obtiene el mejor BIC.
Ahora, para comparar el DAG original con los dos nuevos DAG tabu y hill-climbing, dibuja los tres utilizando la función graphviz.plot. A la luz de los grafos obtenidos en cada caso, ¿cuál te parece que recoge mejor las relaciones causa-efecto entre variables?. Hay que tener en cuenta que los arcos de un grafo no expresan causalidad, sino simplemente dependencia entre variables en términos de probabilidad.
graphviz.plot(dag)

Ahora vuelve a aprender de forma automática el DAG usando tabu y hill-climbing, pero imponiendo las siguientes restricciones: 1. Los arcos viento –> lluvia, tormenta –> granizo y nieve –> nieveSuelo deben quedar reflejados en el DAG. 2. Ningún arco debe unir directamente la neblina con el granizo ni la niebla con la tormenta.
Vuelve a dibujar los DAG resultantes, y a partir del BIC obtenido por cada modelo, determina cuál es el mejor de todos poniéndolos en una tabla.
Computa la fuerza de la relación entre nodos de los dos últimos modelos que combinan nuestra experiencia con el aprendizaje automático.
Comenta brevemente los resultados obtenidos tras combinar nuestro conocimiento con el aprendizaje automático.
LS0tDQp0aXRsZTogIlRyYWJham8gQXV0w7Nub21vOiBSZWRlcyBCYXllc2lhbmFzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMgRGF2aWQgTW9udGVybyBMb2FpemENCg0KQ29udGVuaWRvIGEgZW50cmVnYXI6IFJlc3BvbmRlIGEgbGFzIHByZWd1bnRhcyBwbGFudGVhZGFzIHkgYXBvcnRhIGVsIGPDs2RpZ28gZW4gUiB1dGlsaXphZG8gcGFyYSByZXNwb25kZXJsYXMuIFNlIGRlYmUgZW52aWFyIHVuIMO6bmljbyBkb2N1bWVudG8gY29uIHRleHRvIHkgY8OzZGlnbyBlbnRyZWxhemFkbywgYmllbiBzZWEgZW4gZm9ybWF0byBwZGYgbyBodG1sLg0KDQpGZWNoYSBkZSBlbnRyZWdhOiA3IEFicmlsIDIwMTkNCg0KYGBge3J9DQpyZXF1aXJlKGJubGVhcm4pDQpyZXF1aXJlKGdSYWluKQ0KYGBgDQoNCg0KIyMjIEludHJvZHVjY2nDs24gYSBsYSBwcsOhY3RpY2ENCg0KVXRpbGl6YXJlbW9zIHVuIGNvbmp1bnRvIGRlIGRhdG9zIGRpYXJpb3MgZGUgb2N1cnJlbmNpYSBkZSBtZXRlb3JvcyBlbiBlbCBhZXJvcHVlcnRvIGRlIFBhcmF5YXMgKFNhbnRhbmRlciksIGNvbnNpZGVyYW5kbyB1bmEgc2VyaWUgZGUgdmFyaWFibGVzIG1ldGVvcm9sw7NnaWNhcyByZWdpc3RyYWRhcyBkaWFyaWFtZW50ZSBkdXJhbnRlIHVuIHBlcmlvZG8gYXByb3hpbWFkbyBkZSAxMCBhw7FvcyAoMzI4NiByZWdpc3Ryb3MpLCBlbiBjb25jcmV0bzogcHJlY2lwaXRhY2nDs24sIG5pZXZlLCBncmFuaXpvLCB0b3JtZW50YSwgbmllYmxhLCByb2PDrW8sIGVzY2FyY2hhLCBuaWV2ZSBlbiBlbCBzdWVsbywgbmVibGluYSB5IHJhY2hhIG3DoXhpbWEgZGUgdmllbnRvIHN1cGVyaW9yIGEgNTAga20vaC4gTG9zIGRhdG9zIHNlIGVuY3VlbnRyYW4gZ3VhcmRhZG9zIGVuIGVsIGZpY2hlcm8gZGUgdGV4dG8gbWV0ZW9yby50eHQuIExhcyB2YXJpYWJsZXMgc2UgZW5jdWVudHJhbiBkaXNjcmV0aXphZGFzIGRlIGZvcm1hIGJpbmFyaWEgKG9jdXJyZW5jaWEvYXVzZW5jaWEpLg0KDQpgYGB7cn0NCm1ldGVvcm8gPC0gcmVhZC50YWJsZSgnbWV0ZW9yby50eHQnLGhlYWRlciA9IFRSVUUpDQpgYGANCg0KRW4gZXN0ZSBjYXNvLCB5IGEgdHJhdsOpcyBkZWwgYXNlc29yYW1pZW50byBkZSB1biBtZXRlb3LDs2xvZ28sIHNlIGhhIGVsYWJvcmFkbyBlbCBzaWd1aWVudGUgZGlhZ3JhbWEgYWPDrWNsaWNvIGRpcmlnaWRvIChEQUcpLCBxdWUgcmVjb2dlIGxhcyByZWxhY2lvbmVzIGRlIGRlcGVuZGVuY2lhIGVudHJlIGxhcyB2YXJpYWJsZXM6DQoNCiFbaW1hZ2VuYW1lXShkZXNjYXJnYS5wbmcpDQoNCiMjIyBFamVyY2ljaW8gMSAoMi41IFB1bnRvcykNCg0KQSBwYXJ0aXIgZGVsIGdyYWZvIGRhZG8gcG9yIGVsIGV4cGVydG8sIGRldGVybWluYSBsYSBleHByZXNpw7NuIHF1ZSByZWZsZWphIGxhIGZhY3Rvcml6YWNpw7NuIGRlIGxhIGRpc3RyaWJ1Y2nDs24gZ2xvYmFsLCB5IGVzY3LDrWJlbGEgY29tbyB1bmEgc2VjdWVuY2lhIGRlIGNhcmFjdGVyZXMgZGVsIHRpcG8gZ2VuZXJhZG8gcG9yIGxhIGZ1bmNpw7NuIG1vZGVsc3RyaW5nIGRlbCBwYXF1ZXRlIGRlIFIgYm5sZWFybi4NCg0KYGBge3J9DQpleHByZXNpb24gPSAiW3ZpZW50b11bbGx1dmlhfHZpZW50b11bcm9jaW98dmllbnRvXVt0b3JtZW50YXxsbHV2aWFdW25pZWJsYXxyb2Npbzp2aWVudG9dW2VzY2FyY2hhfHJvY2lvXVtuaWV2ZXx0b3JtZW50YV1bZ3Jhbml6b3x0b3JtZW50YV1bbmVibGluYXxuaWVibGFdW25pZXZlU3VlbG98bmlldmVdIg0KYGBgDQoNCk5PVEE6IENvbW8geWEgc2UgY29tZW50YWRvIGFudGVyaW9ybWVudGUsIGVzIGltcG9ydGFudGUgZXZpdGFyIHRpbGRlcyBhIGxhIGhvcmEgZGUgbm9tYnJhciBsb3Mgbm9kb3MuIFRhbXBvY28gc29uIHJlY29tZW5kYWJsZXMgZXNwYWNpb3MgZW4gYmxhbmNvIG5pIG5pbmfDum4gdGlwbyBkZSBjYXLDoWN0ZXIgZXNwZWNpYWwuDQoNCkEgY29udGludWFjacOzbiwgaW50cm9kdWNlIGVsIERBRyBlbiBSIHV0aWxpemFuZG8gbGEgZGVmaW5pY8Otb24gZGVsIG1vZGVsbyBxdWUgYWNhYmFzIGRlIGNyZWFyDQoNCmBgYHtyfQ0KZGFnID0gbW9kZWwybmV0d29yayhleHByZXNpb24pDQpwbG90KGRhZykNCmBgYA0KDQpSZWFsaXphIHVuYSBsaXN0YSBkZSBsb3MgcGFkcmVzIGUgaGlqb3MgZGUgY2FkYSB1bm8gZGUgbG9zIG5vZG9zDQoNCmBgYHtyfQ0KcGFyZW50c0RBRyA9IGxhcHBseShYID0gbmFtZXMobWV0ZW9ybyksRlVOID0gZnVuY3Rpb24oeCkgcGFyZW50cyhkYWcseCkpDQpjaGlsZHJlbkRBRyA9IGxhcHBseShYID0gbmFtZXMobWV0ZW9ybyksRlVOID0gZnVuY3Rpb24oeCkgY2hpbGRyZW4oZGFnLHgpKQ0KbmFtZXMocGFyZW50c0RBRykgPSBuYW1lcyhtZXRlb3JvKQ0KbmFtZXMoY2hpbGRyZW5EQUcpID0gbmFtZXMobWV0ZW9ybykNCmBgYA0KDQpgYGB7cn0NCnBhcmVudHNEQUcNCmBgYA0KDQpgYGB7cn0NCmNoaWxkcmVuREFHDQpgYGANCg0KUmVhbGl6YSB1bmEgbGlzdGEgZGUgdG9kYXMgbGFzIGNvbmV4aW9uZXMgZnVuZGFtZW50YWxlcyBwcmVzZW50ZXMgZW4gZWwgZ3JhZm8sIHkgcmVhbGl6YSB1bmEgY2xhc2lmaWNhY2nDs24gZGUgY2FkYSB1bmEgZGUgZWxsYXMgZGV0ZXJtaW5hbmRvIHNpIGVzIHVuYSBlc3RydWN0dXJhIGVuIHNlcmllLCBkaXZlcmdlbnRlIG8gY29udmVyZ2VudGUuDQoNCmBgYHtyfQ0KDQpgYGANCg0Kwr9TZSBvYnNlcnZhIGFsZ3VuYSB2LWVzdHJ1Y3R1cmEgZW4gZWwgZ3JhZm8/DQoNCmBgYHtyfQ0KdnN0cnVjdHMoZGFnKQ0KYGBgDQoNCkludGVudGEgaW50cm9kdWNpciB1biBhcmNvIHF1ZSBwYXJ0YSBkZWwgbm9kbyBuZWJsaW5hIHkgc2UgZGlyaWphIGhhY2lhIGVzY2FyY2hhLCB5IG90cm8gcXVlIHZheWEgZGUgZ3Jhbml6byBhIGxsdXZpYS4gQ29tZW50YSBxdcOpIHN1Y2VkZSBlbiBjYWRhIGNhc28sIHkgc2kgZWwgcmVzdWx0YWRvIGRhcsOtYSBsdWdhciBhIHVuYSByZWQgYmF5ZXNpYW5hIHbDoWxpZGEuDQoNCmBgYHtyfQ0KZGFnID0gc2V0LmFyYyhkYWcsZnJvbSA9ICJuZWJsaW5hIix0byA9ICJlc2NhcmNoYSIpDQojZGFnID0gc2V0LmFyYyhkYWcsZnJvbSA9ICJncmFuaXpvIix0byA9ICJsbHV2aWEiKQ0KYGBgDQoNCkRlIG5lYmxpbmEgYSBlc2NhcmNoYSBzZSBwdWVkZSBpbnRyb2R1Y2lyIHVuIGFyY28sIG1pZW50cmFzIHF1ZSBkZSBncmFuaXpvIGEgbGx1dmlhIG5vIHBvcnF1ZSBlbCBncmFmbyBkZWJlIHNlciBhY8OtY2xpY28uDQoNCkRldGVybWluYSBsYSBtYW50YSBkZSBNYXJrb3YgZGVsIG5vZG8gcm9jaW8uDQoNCmBgYHtyfQ0KbWIoZGFnLCJyb2NpbyIpDQpgYGANCg0KTmVibGluYSBlbnRyYSBlbiBsYSBtYW50YSBkZSBNYXJrb3YgeWEgcXVlIGFob3JhIGNvbXBhcnRlIGVsIGhpam8gRXNjYXJjaGEgY29uIFJvY2lvLg0KDQpJbnRyb2R1Y2UgdW4gYXJjbyBxdWUgcGFydGEgZGVsIG5vZG8gTGx1dmlhIHkgc2UgZGlyaWphIGFsIG5vZG8gTmllYmxhLCDCv2NhbWJpYSBsYSBtYW50YSBkZSBNYXJrb3YgZGVsIG5vZG8gcm9jaW8/IEVuIGNhc28gYWZpcm1hdGl2bywgwr9jw7NtbyBsbyBoYWNlPw0KDQpgYGB7cn0NCmRhZyA9IHNldC5hcmMoZGFnLGZyb20gPSAibGx1dmlhIix0byA9ICJuaWVibGEiKQ0KDQptYihkYWcsInJvY2lvIikNCmBgYA0KDQpMbHV2aWEgc2UgaW50cm9kdWNlIGVuIGxhIG1hbnRhIGRlIE1hcmtvdiB5YSBxdWUgY29tcGFydGUgdW4gaGlqbyAoTmllYmxhKSBjb24gUm9jaW8uDQoNClBhcmEgY29udGludWFyLCBlbGltaW5hIGVsIGFyY28gZGVmaW5pZG8gYW50ZXJpb3JtZW50ZSBlbnRyZSBsb3Mgbm9kb3MgTGx1dmlhIHkgTmllYmxhLCByZWN1cGVyYW5kbyBlbCBEQUcgb3JpZ2luYWwuDQoNCmBgYHtyfQ0KZGFnID0gZHJvcC5hcmMoZGFnLGZyb20gPSAibGx1dmlhIix0byA9ICJuaWVibGEiKQ0KZGFnID0gZHJvcC5hcmMoZGFnLGZyb20gPSAibmVibGluYSIsdG8gPSAiZXNjYXJjaGEiKQ0KYGBgDQoNCiMjIyBFamVyY2ljaW8gMiAoMi41IFB1bnRvcykNCg0KQ29uc2lkZXJhbmRvIGxhIGluZm9ybWFjacOzbiBwcm9wb3JjaW9uYWRhIHBvciBlbCBjb25qdW50byBkZSBkYXRvcyBtZXRlb3IsIGNvbnN0cnV5ZSBsYSByZWQgYmF5ZXNpYW5hIHV0aWxpemFuZG8gZWwgbcOpdG9kbyBiYXllc2lhbm8gZGUgZXN0aW1hY2nDs24gcGFyYW3DqXRyaWNhLg0KDQpgYGB7cn0NCmJuID0gYm4uZml0KGRhZywgZGF0YSA9IG1ldGVvcm8sIG1ldGhvZCA9ICJiYXllcyIpDQpgYGANCg0Kwr9DdcOhbCBzZXLDrWEgZWwgbsO6bWVybyBwb3RlbmNpYWwgZGUgcGFyw6FtZXRyb3MgKHNpbiB1c2FyIGxhIHJlZCBiYXllc2lhbmEpIGRlbCBtb2RlbG8gcGFyYSBjYWxjdWxhciBsYSBwcm9iYWJpbGlkYWQgZ2xvYmFsIHNpIG5vIHV0aWxpemFzZW1vcyBlbCBEQUc/DQoNCmBgYHtyfQ0KMl4obmNvbChtZXRlb3JvKSkNCmBgYA0KDQrCv0N1w6FudG9zIHBhcsOhbWV0cm9zIHRpZW5lIGxhIGRpc3RyaWJ1Y2nDs24gZ2xvYmFsIGRhZGEgcG9yIGxhIHJlZCBiYXllc2lhbmE/DQoNCmBgYHtyfQ0KbnBhcmFtcyhibikNCmBgYA0KDQpEZXRlcm1pbmEgZWwgbsO6bWVybyBkZSBwYXLDoW1ldHJvcyBhc29jaWFkbyBhIGNhZGEgdW5hIGRlIGxhcyBkaXN0cmlidWNpb25lcyBsb2NhbGVzDQoNCmBgYHtyfQ0KDQpgYGANCg0KT2J0w6luIGxhcyB0YWJsYXMgZGUgcHJvYmFiaWxpZGFkIGNvbmRpY2lvbmFsIGFzb2NpYWRhcyBsb3Mgbm9kb3MgZ3Jhbml6byB5IG5pZWJsYS4gQWhvcmEgcmVwcmVzZW50YSBsYSBpbmZvcm1hY2nDs24gZGUgY2FkYSB0YWJsYSBlbiBzZW5kb3MgZ3LDoWZpY29zLg0KDQpgYGB7cn0NCmJuJGdyYW5pem8NCmJuLmZpdC5iYXJjaGFydChibiRncmFuaXpvLCBtYWluID0gIkdyYW5pem8iLCB4bGFiID0gIlByKGdyYW5pem98dG9ybWVudGEpIiwgeWxhYiA9ICJFc3RhZG8gZGUgR3Jhbml6byIpDQpgYGANCg0KYGBge3J9DQpibiRuaWVibGENCmJuLmZpdC5iYXJjaGFydChibiRuaWVibGEsIG1haW4gPSAiTmllYmxhIiwgeGxhYiA9ICJQcihuaWVibGF8dmllbnRvLHJvY2lvKSIsIHlsYWIgPSAiRXN0YWRvIGRlIE5pZWJsYSIpDQpgYGANCg0KIyMjIEVqZXJjaWNpbyAzICgyLjUgUHVudG9zKQ0KDQpVbmEgdmV6IGNvbnN0cnXDrWRhIGxhIHJlZCBoZW1vcyBlc3RhYmxlY2lkbyBsYSBiYXNlIGRlIGNvbm9jaW1pZW50byBkZWwgc2lzdGVtYSBpbnRlbGlnZW50ZS4gQSBjb250aW51YWNpw7NuIHNlIHB1ZWRlIGNhbGN1bGFyIGxhIHByb2JhYmlsaWRhZCBkZSBjdWFscXVpZXIgdmFyaWFibGUgbyBjb25qdW50byBkZSB2YXJpYWJsZXMgY29uZGljaW9uYWRhcyBhIGN1YWxxdWllciBldmlkZW5jaWEgcXVlIHNlIHRlbmdhIGRpc3BvbmlibGUgcGFyYSB1biBwcm9ibGVtYSBkYWRvLCBlcyBkZWNpciwgcmVhbGl6YXIgbGEgaW5mZXJlbmNpYS4NCg0KT2JzZXJ2YSBsYSBlc3RydWN0dXJhIGRlbCBEQUcgeSByZXBhc2EgbGEgdGVvcsOtYSB5IGVsIGNvbmNlcHRvIGRlIGQtc2VwYXJhY2nDs24uIFJlc3BvbmRlIHJhem9uYWRhbWVudGUgc2kgbGFzIHNpZ3VpZW50ZXMgYWZpcm1hY2lvbmVzIHNvbiB2ZXJkYWRlcmFzIG8gZmFsc2FzLCB1dGlsaXphbmRvIMO6bmljYW1lbnRlIGxhIGVzdHJ1Y3R1cmEgZGVsIERBRyB5IGVsIGNvbmNlcHRvIGRlIGQtc2VwYXJhY2nDs246DQoNCkxhIG5pZXZlIHkgZWwgZ3Jhbml6byBzb24gZmVuw7NtZW5vcyBpbmRlcGVuZGllbnRlcyBhIHByaW9yaTogRmFsc28NCg0KQ29uZGljacOzbjogKlZhY8OtbyoNCk5pZXZlIDwtIFRvcm1lbnRhIC0+IEdyYW5pem86IE5pZXZlIHkgZ3Jhbml6byBubyBlc3TDoW4gZC1zZXBhcmFkb3MgeWEgcXVlIG5vIGhheSB2LWVzdHJ1Y3R1cmFzIHkgVG9ybWVudGEgbm8gdGllbmUgYXJjb3MgY29udmVyZ2VudGVzIHBlcm8gbm8gZXN0w6EgZW4gZWwgY29uanVudG8gY29uZGljacOzbiAoY29uanVudG8gKnZhY8OtbyopLg0KDQpgYGB7cn0NCmRzZXAoYm4sIm5pZXZlIiwiZ3Jhbml6byIpDQpgYGANCg0KTGEgbmlldmUgeSBlbCBncmFuaXpvIHNvbiBmZW7Ds21lbm9zIGluZGVwZW5kaWVudGVzIGRhZG8gcXVlIGhheWEgaGFiaWRvIHRvcm1lbnRhOiBWZXJkYWRlcm8NCg0KQ29uZGljacOzbjogKlRvcm1lbnRhKg0KTmlldmUgPC0gKlRvcm1lbnRhKiAtPiBHcmFuaXpvOiBOaWV2ZSB5IGdyYW5pem8gZXN0w6FuIGQtc2VwYXJhZG9zIHlhIHF1ZSAqVG9ybWVudGEqIG5vIHRpZW5lIGFyY29zIGNvbnZlcmdlbnRlcyB5IGVzdMOhIGVuIGVsIGNvbmp1bnRvIGNvbmRpY2nDs24gKGNvbmp1bnRvICpUb3JtZW50YSopLg0KDQpgYGB7cn0NCmRzZXAoYm4sIm5pZXZlIiwiZ3Jhbml6byIsInRvcm1lbnRhIikNCmBgYA0KDQpMYSBuaWV2ZSBlbiBlbCBzdWVsbyB5IGxhIG5lYmxpbmEgc29uIGZlbsOzbWVub3MgaW5kZXBlbmRpZW50ZXM6IEZhbHNvDQoNCkNvbmRpY2nDs246ICpWYWPDrW8qDQoNCk5pZXZlU3VlbG8gPC0gTmlldmUgPC0gVG9ybWVudGEgPC0gTGx1dmlhIDwtIFZpZW50byAtPiBOaWVibGEgLT4gTmVibGluYTogTmlldmVTdWVsbyB5IGdyYW5pem8gbm8gZXN0w6FuIGQtc2VwYXJhZG9zIHlhIHF1ZSBubyBoYXkgdi1lc3RydWN0dXJhcyB5IGVsIG5vZG8gVmllbnRvIG5vIHRpZW5lIGFyY29zIGNvbnZlcmdlbnRlcyBwZXJvIG5vIGVzdMOhIGVuIGVsIGNvbmp1bnRvIGNvbmRpY2nDs24gKGNvbmp1bnRvICp2YWPDrW8qKS4NCg0KTmlldmVTdWVsbyA8LSBOaWV2ZSA8LSBUb3JtZW50YSA8LSBMbHV2aWEgPC0gVmllbnRvIC0+IFJvY2lvIC0+IE5pZWJsYSAtPiBOZWJsaW5hOiBOaWV2ZVN1ZWxvIHkgZ3Jhbml6byBubyBlc3TDoW4gZC1zZXBhcmFkb3MgeWEgcXVlIG5vIGhheSB2LWVzdHJ1Y3R1cmFzIHkgZWwgbm9kbyBWaWVudG8gbm8gdGllbmUgYXJjb3MgY29udmVyZ2VudGVzIHBlcm8gbm8gZXN0w6EgZW4gZWwgY29uanVudG8gY29uZGljacOzbiAoY29uanVudG8gKnZhY8OtbyopLg0KDQpgYGB7cn0NCmRzZXAoYm4sIm5pZXZlU3VlbG8iLCJuZWJsaW5hIikNCmBgYA0KDQpMYSBuaWV2ZSBlbiBlbCBzdWVsbyB5IGxhIG5lYmxpbmEgc29uIGZlbsOzbWVub3MgaW5kZXBlbmRpZW50ZXMgZGFkbyBxdWUgaGF5YSBoYWJpZG8gdG9ybWVudGE6IFZlcmRhZGVybw0KDQpDb25kaWNpw7NuOiAqVG9ybWVudGEqDQoNCk5pZXZlU3VlbG8gPC0gTmlldmUgPC0gKlRvcm1lbnRhKiA8LSBMbHV2aWEgPC0gVmllbnRvIC0+IE5pZWJsYSAtPiBOZWJsaW5hOiBOaWV2ZVN1ZWxvIHkgZ3Jhbml6byBlc3TDoW4gZC1zZXBhcmFkb3MgeWEgcXVlIG5vIGhheSB2LWVzdHJ1Y3R1cmFzIHkgZWwgbm9kbyBWaWVudG8gbm8gdGllbmUgYXJjb3MgY29udmVyZ2VudGVzIHBlcm8gbm8gZXN0w6EgZW4gZWwgY29uanVudG8gY29uZGljacOzbiAoY29uanVudG8gKlRvcm1lbnRhKikuDQoNCk5pZXZlU3VlbG8gPC0gTmlldmUgPC0gKlRvcm1lbnRhKiA8LSBMbHV2aWEgPC0gVmllbnRvIC0+IFJvY2lvIC0+IE5pZWJsYSAtPiBOZWJsaW5hOiBOaWV2ZVN1ZWxvIHkgZ3Jhbml6byBlc3TDoW4gZC1zZXBhcmFkb3MgeWEgcXVlIG5vIGhheSB2LWVzdHJ1Y3R1cmFzIHkgZWwgbm9kbyBWaWVudG8gbm8gdGllbmUgYXJjb3MgY29udmVyZ2VudGVzIHBlcm8gbm8gZXN0w6EgZW4gZWwgY29uanVudG8gY29uZGljacOzbiAoY29uanVudG8gKlRvcm1lbnRhKikuDQoNCmBgYHtyfQ0KZHNlcChibiwibmlldmVTdWVsbyIsIm5lYmxpbmEiLCJ0b3JtZW50YSIpDQpgYGANCg0KTk9UQTogRW4gZXN0ZSBhcGFydGFkbyBzZSBkZWJlcsOhIGFwbGljYXIgbGEgaW5mZXJlbmNpYSBleGFjdGEuIENvbm9jaWRvIHF1ZSBlbiB1biBkw61hIGRhZG8gc2UgaGFuIHByb2R1Y2lkbyB0b3JtZW50YXMsIGNhbGN1bGEgY8OzbW8gYWZlY3RhIGVzdGUgaGVjaG8gYSBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHNlIHByb2R1emNhbiBsb3Mgc2lndWllbnRlcyBmZW7Ds21lbm9zIG1ldGVvcm9sw7NnaWNvczoNCg0KYGBge3J9DQpqdW5jdGlvbiA9IGNvbXBpbGUoYXMuZ3JhaW4oYm4pKQ0KdG9ybWVudGEucyA9IHNldEV2aWRlbmNlKGp1bmN0aW9uLG5vZGVzID0gInRvcm1lbnRhIiwgc3RhdGVzID0gInMiKQ0KYGBgDQoNClF1ZSBsbHVldmEsIFAobGx1dmlhPXN8dG9ybWVudGE9cykNCg0KYGBge3J9DQpxdWVyeWdyYWluKHRvcm1lbnRhLnMsIG5vZGVzID0gImxsdXZpYSIpJGxsdXZpYVsicyJdDQpgYGANCg0KUXVlIGhheWEgcmFjaGFzIGRlIHZpZW50byBzdXBlcmlvcmVzIGEgNTAgS20vaCwgUCh2aWVudG89c3x0b3JtZW50YT1zKQ0KDQpgYGB7cn0NCnF1ZXJ5Z3JhaW4odG9ybWVudGEucywgbm9kZXMgPSAidmllbnRvIikkdmllbnRvWyJzIl0NCmBgYA0KDQpRdWUgbGx1ZXZhIHkgcXVlIGFkZW3DoXMgbGFzIHJhY2hhcyBkZSB2aWVudG8gc3VwZXJlbiBsb3MgNTAgS20vaCwgUChsbHV2aWE9cyx2aWVudG89c3x0b3JtZW50YT1zKSBbZW4gZWwgZW51bmNpYWRvIG9yaWdpbmFsIGFwYXJlY8OtYSB2aWVudG89biwgZGFkbyBlbCBlbnVuY2lhZG8gbG8gY2FtYmnDqSBwb3IgdmllbnRvPXNdDQoNCmBgYHtyfQ0KcXVlcnlncmFpbih0b3JtZW50YS5zLCBub2RlcyA9IGMoImxsdXZpYSIsInZpZW50byIpLHR5cGUgPSAiam9pbnQiKVsicyIsInMiXQ0KYGBgDQoNCkEgcGFydGlyIGRlIGxhIGluZm9ybWFjacOzbiByZXZlbGFkYSBwb3IgbGEgcmVkIGJheWVzaWFuYSwgc2FiaWVuZG8gcXVlIHVuIGTDrWEgc2UgcHJvZHVjZW4gdG9ybWVudGFzOg0KDQrCv0hheSBtYXlvciBwcm9iYWJpbGlkYWQgZGUgcXVlIGxsdWV2YSBjdWFuZG8gc2UgcHJvZHVjZW4gdG9ybWVudGFzIHF1ZSBjdWFscXVpZXIgb3RybyBkw61hPw0KDQpgYGB7cn0NCnRvcm1lbnRhLm4gPSBzZXRFdmlkZW5jZShqdW5jdGlvbixub2RlcyA9ICJ0b3JtZW50YSIsIHN0YXRlcyA9ICJuIikNCnF1ZXJ5Z3JhaW4odG9ybWVudGEubiwgbm9kZXMgPSAibGx1dmlhIikkbGx1dmlhWyJzIl0NCmBgYA0KDQpTw60sIGhheSBtYXlvciBwcm9iYWJpbGlkYWQgZGUgcXVlIGxsdWV2YSBjdWFuZG8gc2UgcHJvZHVjZW4gdG9ybWVudGFzICgwLjkxKSBxdWUgY3VhbmRvIG5vICgwLjUyKS4NCg0Kwr9BdW1lbnRhIG8gZGlzbWludXllIGxhIHByb2JhYmlsaWRhZCBkZSB0ZW5lciByYWNoYXMgZGUgdmllbnRvIG1heW9yZXMgZGUgNTAgS20vaCBjdWFuZG8gc2UgcHJvZHVjZSB0b3JtZW50YT8gwr9DdcOhbnRvPw0KDQpgYGB7cn0NCnF1ZXJ5Z3JhaW4odG9ybWVudGEubiwgbm9kZXMgPSAidmllbnRvIikkdmllbnRvWyJzIl0NCmBgYA0KDQpBdW1lbnRhIGxhIHByb2JhYmlsaWRhZCBkZSAwLjEzIGEgMC4yMSBkZSB0ZW5lciByYWNoYXMgZGUgdmllbnRvIG1heW9yZXMgZGUgNTAgS20vaCBjdWFuZG8gc2UgcHJvZHVjZSB1bmEgdG9ybWVudGEuDQoNCkRhZG8gZWwgZWplcmNpY2lvIGFudGVyaW9yLCByZXBldGlyIGFob3JhIGVsIGVqZXJjaWNpbyBtZWRpYW50ZSBpbmZlcmVuY2lhIGFwcm94aW1hZGEsIGNhbGN1bGFuZG8gcGFyYSBjYWRhIHVuYSBkZSBsYXMgZXN0aW1hY2lvbmVzIDEwMCByZWFsaXphY2lvbmVzLiBQYXJhIGNhZGEgdW5hIGRlIGxhcyByZXNwdWVzdGFzIGFudGVyaW9yZXMsIHJlcHJlc2VudGEgdW4gZGlhZ3JhbWEgZGUgY2FqYXMgcXVlIHJlcHJlc2VudGUgbGEgZGlzcGVyc2nDs24gZW4gbGEgZXN0aW1hY2nDs24gZGUgbGEgcHJvYmFiaWxpZGFkLCBtYXJjYW5kbyBhZGVtw6FzIGVsIHZhbG9yIG9idGVuaWRvIGRlIG1hbmVyYSBleGFjdGEgZW4gZWwgYXBhcnRhZG8gYW50ZXJpb3I6DQoNClF1ZSBsbHVldmEsIFAobGx1dmlhPXN8dG9ybWVudGE9cykNCg0KYGBge3J9DQppbmYuZXhhY3RhID0gcXVlcnlncmFpbih0b3JtZW50YS5zLCBub2RlcyA9ICJsbHV2aWEiKSRsbHV2aWFbInMiXQ0KaW5mLmFwcm94ID0gTlVMTA0KZm9yKGkgaW4gMToxMDApIGluZi5hcHJveCA9IGMoaW5mLmFwcm94LGNwcXVlcnkoYm4sZXZlbnQgPSAobGx1dmlhID09ICJzIiksZXZpZGVuY2UgPSAodG9ybWVudGEgPT0gInMiKSkpDQpib3hwbG90KGluZi5hcHJveCxtYWluID0gIihQKGxsdXZpYT1zfHRvcm1lbnRhPXMpKSIpDQphYmxpbmUoaCA9IGluZi5leGFjdGEsY29sID0gInJlZCIpDQpsZWdlbmQoInRvcGxlZnQiLGx0eSA9IDEsY29sID0gInJlZCIsbGVnZW5kID0gIkluZmVyZW5jaWEgRXhhY3RhIikNCmBgYA0KDQpRdWUgaGF5YSByYWNoYXMgZGUgdmllbnRvIHN1cGVyaW9yZXMgYSA1MCBLbS9oLCBQKHZpZW50bz1zfHRvcm1lbnRhPXMpDQoNCmBgYHtyfQ0KaW5mLmV4YWN0YSA9IHF1ZXJ5Z3JhaW4odG9ybWVudGEucywgbm9kZXMgPSAidmllbnRvIikkdmllbnRvWyJzIl0NCmluZi5hcHJveCA9IE5VTEwNCmZvcihpIGluIDE6MTAwKSBpbmYuYXByb3ggPSBjKGluZi5hcHJveCxjcHF1ZXJ5KGJuLGV2ZW50ID0gKHZpZW50byA9PSAicyIpLGV2aWRlbmNlID0gKHRvcm1lbnRhID09ICJzIikpKQ0KYm94cGxvdChpbmYuYXByb3gsbWFpbiA9ICIoUCh2aWVudG89c3x0b3JtZW50YT1zKSkiKQ0KYWJsaW5lKGggPSBpbmYuZXhhY3RhLGNvbCA9ICJyZWQiKQ0KbGVnZW5kKCJ0b3BsZWZ0IixsdHkgPSAxLGNvbCA9ICJyZWQiLGxlZ2VuZCA9ICJJbmZlcmVuY2lhIEV4YWN0YSIpDQpgYGANCg0KUXVlIGxsdWV2YSB5IHF1ZSBhZGVtw6FzIGxhcyByYWNoYXMgZGUgdmllbnRvIHN1cGVyZW4gbG9zIDUwIEttL2gsIFAobGx1dmlhPXMsdmllbnRvPXN8dG9ybWVudGE9cykgW2VuIGVsIGVudW5jaWFkbyBvcmlnaW5hbCBhcGFyZWPDrWEgdmllbnRvPW4sIGRhZG8gZWwgZW51bmNpYWRvIGxvIGNhbWJpw6kgcG9yIHZpZW50bz1zXQ0KDQpgYGB7cn0NCmluZi5leGFjdGEgPSBxdWVyeWdyYWluKHRvcm1lbnRhLnMsIG5vZGVzID0gYygibGx1dmlhIiwidmllbnRvIiksdHlwZSA9ICJqb2ludCIpWyJzIiwicyJdDQppbmYuYXByb3ggPSBOVUxMDQpmb3IoaSBpbiAxOjEwMCkgaW5mLmFwcm94ID0gYyhpbmYuYXByb3gsY3BxdWVyeShibixldmVudCA9IChsbHV2aWEgPT0gInMiKSAmICh2aWVudG8gPT0gInMiKSxldmlkZW5jZSA9ICh0b3JtZW50YSA9PSAicyIpKSkNCmJveHBsb3QoaW5mLmFwcm94LG1haW4gPSAiKFAobGx1dmlhPXMsdmllbnRvPXN8dG9ybWVudGE9cykpIikNCmFibGluZShoID0gaW5mLmV4YWN0YSxjb2wgPSAicmVkIikNCmxlZ2VuZCgidG9wbGVmdCIsbHR5ID0gMSxjb2wgPSAicmVkIixsZWdlbmQgPSAiSW5mZXJlbmNpYSBFeGFjdGEiKQ0KYGBgDQoNCsK/SGF5IG1heW9yIHByb2JhYmlsaWRhZCBkZSBxdWUgbGx1ZXZhIGN1YW5kbyBzZSBwcm9kdWNlbiB0b3JtZW50YXMgcXVlIGN1YWxxdWllciBvdHJvIGTDrWE/DQoNCmBgYHtyfQ0KaW5mLmV4YWN0YS5uID0gcXVlcnlncmFpbih0b3JtZW50YS5uLCBub2RlcyA9ICJsbHV2aWEiKSRsbHV2aWFbInMiXQ0KaW5mLmV4YWN0YS5zID0gcXVlcnlncmFpbih0b3JtZW50YS5zLCBub2RlcyA9ICJsbHV2aWEiKSRsbHV2aWFbInMiXQ0KDQppbmYuYXByb3gubiA9IE5VTEwNCmluZi5hcHJveC5zID0gTlVMTA0KDQpmb3IoaSBpbiAxOjEwMCl7DQogIGluZi5hcHJveC5zID0gYyhpbmYuYXByb3gucyxjcHF1ZXJ5KGJuLGV2ZW50ID0gKGxsdXZpYSA9PSAicyIpLGV2aWRlbmNlID0gKHRvcm1lbnRhID09ICJzIikpKQ0KICBpbmYuYXByb3gubiA9IGMoaW5mLmFwcm94Lm4sY3BxdWVyeShibixldmVudCA9IChsbHV2aWEgPT0gInMiKSxldmlkZW5jZSA9ICh0b3JtZW50YSA9PSAibiIpKSkNCn0NCg0KcGFyKG1mcm93ID0gYygxLDIpKQ0KDQpib3hwbG90KGluZi5hcHJveC5uLG1haW4gPSAiKFAobGx1dmlhPXN8dG9ybWVudGE9bikpIix5bGltID0gYygwLDEpKQ0KYWJsaW5lKGggPSBpbmYuZXhhY3RhLm4sY29sID0gInJlZCIpDQpsZWdlbmQoInRvcGxlZnQiLGx0eSA9IDEsY29sID0gInJlZCIsbGVnZW5kID0gIkluZmVyZW5jaWEgRXhhY3RhIikNCg0KYm94cGxvdChpbmYuYXByb3gucyxtYWluID0gIihQKGxsdXZpYT1zfHRvcm1lbnRhPXMpKSIseWxpbSA9IGMoMCwxKSkNCmFibGluZShoID0gaW5mLmV4YWN0YS5zLGNvbCA9ICJibHVlIikNCmxlZ2VuZCgiYm90dG9tbGVmdCIsbHR5ID0gMSxjb2wgPSAiYmx1ZSIsbGVnZW5kID0gIkluZmVyZW5jaWEgRXhhY3RhIikNCmBgYA0KDQpTw60gZXhpc3RlIHVuYSBtYXlvciBwcm9iYWJpbGlkYWQgZGUgcXVlIGxsdWV2YSBjdWFuZG8gaGF5IHRvcm1lbnRhcy4NCg0Kwr9BdW1lbnRhIG8gZGlzbWludXllIGxhIHByb2JhYmlsaWRhZCBkZSB0ZW5lciByYWNoYXMgZGUgdmllbnRvIG1heW9yZXMgZGUgNTAgS20vaCBjdWFuZG8gc2UgcHJvZHVjZSB0b3JtZW50YT8gwr9DdcOhbnRvPw0KDQpgYGB7cn0NCmluZi5leGFjdGEubiA9IHF1ZXJ5Z3JhaW4odG9ybWVudGEubiwgbm9kZXMgPSAidmllbnRvIikkdmllbnRvWyJzIl0NCmluZi5leGFjdGEucyA9IHF1ZXJ5Z3JhaW4odG9ybWVudGEucywgbm9kZXMgPSAidmllbnRvIikkdmllbnRvWyJzIl0NCg0KaW5mLmFwcm94Lm4gPSBOVUxMDQppbmYuYXByb3gucyA9IE5VTEwNCg0KZm9yKGkgaW4gMToxMDApew0KICBpbmYuYXByb3gucyA9IGMoaW5mLmFwcm94LnMsY3BxdWVyeShibixldmVudCA9ICh2aWVudG8gPT0gInMiKSxldmlkZW5jZSA9ICh0b3JtZW50YSA9PSAicyIpKSkNCiAgaW5mLmFwcm94Lm4gPSBjKGluZi5hcHJveC5uLGNwcXVlcnkoYm4sZXZlbnQgPSAodmllbnRvID09ICJzIiksZXZpZGVuY2UgPSAodG9ybWVudGEgPT0gIm4iKSkpDQp9DQoNCnBhcihtZnJvdyA9IGMoMSwyKSkNCg0KYm94cGxvdChpbmYuYXByb3gubixtYWluID0gIihQKHZpZW50bz1zfHRvcm1lbnRhPW4pKSIseWxpbSA9IGMoMCwxKSkNCmFibGluZShoID0gaW5mLmV4YWN0YS5uLGNvbCA9ICJyZWQiKQ0KbGVnZW5kKCJ0b3BsZWZ0IixsdHkgPSAxLGNvbCA9ICJyZWQiLGxlZ2VuZCA9ICJJbmZlcmVuY2lhIEV4YWN0YSIpDQoNCmJveHBsb3QoaW5mLmFwcm94LnMsbWFpbiA9ICIoUCh2aWVudG89c3x0b3JtZW50YT1zKSkiLHlsaW0gPSBjKDAsMSkpDQphYmxpbmUoaCA9IGluZi5leGFjdGEucyxjb2wgPSAiYmx1ZSIpDQpsZWdlbmQoInRvcGxlZnQiLGx0eSA9IDEsY29sID0gImJsdWUiLGxlZ2VuZCA9ICJJbmZlcmVuY2lhIEV4YWN0YSIpDQpgYGANCg0KQXVtZW50YSBsYSBwcm9iYWJpbGlkYWQsIHNpbiBlbWJhcmdvLCBwYXJhIGluZmVyZW5jaWFzIGFwcm94aW1hZGFzLCBlcyBuZWNlc2FyaW8gcmVhbGl6YXIgdmFyaWFzIHBydWViYXMgcGFyYSBhY2VyY2Fybm9zIG3DoXMgYSB1biB2YWxvciBlc3RpbWFkby4NCg0KIyMjIEVqZXJjaWNpbyA0ICgyLjUgUHVudG9zKQ0KDQpDb21vIGhlbW9zIHZpc3RvLCBlcyBwb3NpYmxlIHJlYWxpemFyIHVuIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvIGRlIGxhIGVzdHJ1Y3R1cmEgZGVsIERBRyBhIHBhcnRpciBkZSBsb3MgZGF0b3MsIHVzYW5kbyBhbGdvcml0bW9zIGVzcGVjw61maWNvcyBwYXJhIGVsbG8uIER1cmFudGUgbGFzIGNsYXNlcyBoZW1vcyB2aXN0byBlbCBlamVtcGxvIGRlbCBhbGdvcml0bW8gaGlsbC1jbGltYmluZywgYXVucXVlIGNvbW8gdmltb3MsIGhheSBvdHJhcyBwb3NpYmlsaWRhZGVzLiBUYW1iaWVuIGhlbW9zIHZpc3RvIHF1ZSBwb2RlbW9zIGNvbWJpbmFyIG51ZXN0cmEgZXhwZXJpZW5jaWEgeSBlbCBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBkZWZpbmllbmRvIHByZXZpYW1lbnRlIHJlbGFjaW9uZXMgZW50cmUgdmFyaWFibGVzIHF1ZSBxdWVyZW1vcyBpbnRyb2R1Y2lyIG8gZGVzY2FydGFyIGVuIGVsIERBRyByZXN1bHRhbnRlLiBBZGVtw6FzLCBzZSBoYSBleHBsaWNhZG8gcXVlIGV4aXN0ZW4gc2NvcmVzIHF1ZSBzaXJ2ZW4gY29tbyBjcml0ZXJpbyBwYXJhIGV2YWx1YXIgbGEgZnVlcnphIGRlIGxhIGRlcGVuZGVuZGVuY2lhIGVudHJlIG5vZG9zIGRlIGxhIHJlZCB5IGNvbXBhcmFyIGxhIGJvbmRhZCBkZSBhanVzdGUgZGVsIG1vZGVsby4NCg0KRXZhbMO6YSBsYSBzaWduaWZpY2FjacOzbiBkZSBsb3MgYXJjb3MgZGlidWphZG9zIHBvciBlbCBleHBlcnRvIGVuIGVsIGFjdHVhbCBEQUcgdXRpbGl6YW5kbyBlbCBlc3RhZMOtc3RpY28gz4cyLiDCv0hheSBhbGfDum4gYXJjbyBubyBzaWduaWZpY2F0aXZvPyANCg0KYGBge3J9DQpzdHJlbmd0aCA9IGFyYy5zdHJlbmd0aChkYWcsIGRhdGEgPSBtZXRlb3JvLCBjcml0ZXJpb24gPSAieDIiKQ0Kc3RyZW5ndGgNCmBgYA0KDQpObyBoYXkgYXJjb3MgcXVlIG5vIHNlYW4gc2lnbmlmaWNhdGl2b3MgYSB1biBuaXZlbCBkZSBjb25maWFuemEgZGVsIDk1JS4NCg0Kwr9DdcOhbGVzIHNvbiBsb3MgdHJlcyBwYXJlcyBkZSBub2RvcyBxdWUgcHJlc2VudGFuIHVuIGFyY28gZGUgdW5pw7NuIG3DoXMgZnVlcnRlPw0KDQpgYGB7cn0NCmhlYWQoc3RyZW5ndGhbb3JkZXIoc3RyZW5ndGgkc3RyZW5ndGgpLF0sMykNCmBgYA0KDQpBZGVtw6FzIGRlbCBhbGdvcml0bW8gaGlsbC1jbGltYmluZywgZXhpc3RlIG90cm8gYWxnb3JpdG1vIHBvcHVsYXIgZGUgdGlwbyDigJx2b3JheuKAnSBkZW5vbWluYWRvIFRhYnUgc2VhcmNoLiBFbiBibmxlYXJuIHNlIGVuY3VlbnRyYSBpbXBsZW1lbnRhZG8gYSB0cmF2ZXMgZGUgbGEgZnVuY2nDs24gdGFidSwgeSBsb3MgYXJndW1lbnRvcyBkZSBlbnRyYWRhIHNvbiBzaW1pbGFyZXMgYSBsb3MgdmlzdG9zIHBhcmEgaGlsbC1jbGltYmluZy4NCg0KQ29tcGFyYSBlbCBzY29yZSBnbG9iYWwgKEJJQykgb2J0ZW5pZG8gcG9yIGVsIERBRyBpbmljaWFsLCBjb24gbG9zIG9idGVuaWRvcyBwb3IgbG9zIERBRyBhcHJlbmRpZG9zIGRlIGZvcm1hIGF1dG9tw6F0aWNhIGNvbiBsb3MgYWxnb3JpdG1vcyB0YWJ1IHkgaGlsbC1jbGltYmluZy4gwr9DdcOhbCBvYnRpZW5lIG1lam9yIHNjb3JlPw0KDQpgYGB7cn0NCmJpYy5kYWcuaW5pY2lhbCA9IHNjb3JlKGRhZyxkYXRhID0gbWV0ZW9ybykNCg0KZGFnLmhjID0gbW9kZWwybmV0d29yayhtb2RlbHN0cmluZyhoYyhtZXRlb3JvKSkpDQpkYWcudGFidSA9IG1vZGVsMm5ldHdvcmsobW9kZWxzdHJpbmcodGFidShtZXRlb3JvKSkpDQoNCmJpYy5kYWcuaGMgPSBzY29yZShkYWcuaGMsZGF0YSA9IG1ldGVvcm8pDQpiaWMuZGFnLnRhYnUgPSBzY29yZShkYWcudGFidSxkYXRhID0gbWV0ZW9ybykNCg0KYmljcyA9IGMoYmljLmRhZy5pbmljaWFsLGJpYy5kYWcuaGMsYmljLmRhZy50YWJ1KQ0KbmFtZXMoYmljcykgPSBjKCJJbmljaWFsIiwiSEMiLCJUYWJ1IikNCmJpY3MNCmBgYA0KDQpFbCBtw6l0b2RvIGRlIFRhYnUgb2J0aWVuZSBlbCBtZWpvciBCSUMuDQoNCkFob3JhLCBwYXJhIGNvbXBhcmFyIGVsIERBRyBvcmlnaW5hbCBjb24gbG9zIGRvcyBudWV2b3MgREFHIHRhYnUgeSBoaWxsLWNsaW1iaW5nLCBkaWJ1amEgbG9zIHRyZXMgdXRpbGl6YW5kbyBsYSBmdW5jacOzbiBncmFwaHZpei5wbG90LiBBIGxhIGx1eiBkZSBsb3MgZ3JhZm9zIG9idGVuaWRvcyBlbiBjYWRhIGNhc28sIMK/Y3XDoWwgdGUgcGFyZWNlIHF1ZSByZWNvZ2UgbWVqb3IgbGFzIHJlbGFjaW9uZXMgY2F1c2EtZWZlY3RvIGVudHJlIHZhcmlhYmxlcz8uIEhheSBxdWUgdGVuZXIgZW4gY3VlbnRhIHF1ZSBsb3MgYXJjb3MgZGUgdW4gZ3JhZm8gbm8gZXhwcmVzYW4gY2F1c2FsaWRhZCwgc2lubyBzaW1wbGVtZW50ZSBkZXBlbmRlbmNpYSBlbnRyZSB2YXJpYWJsZXMgZW4gdMOpcm1pbm9zIGRlIHByb2JhYmlsaWRhZC4NCg0KYGBge3J9DQpncmFwaHZpei5wbG90KGRhZykNCmBgYA0KDQoNCkFob3JhIHZ1ZWx2ZSBhIGFwcmVuZGVyIGRlIGZvcm1hIGF1dG9tw6F0aWNhIGVsIERBRyB1c2FuZG8gdGFidSB5IGhpbGwtY2xpbWJpbmcsIHBlcm8gaW1wb25pZW5kbyBsYXMgc2lndWllbnRlcyByZXN0cmljY2lvbmVzOiAxLiBMb3MgYXJjb3MgdmllbnRvIC0tPiBsbHV2aWEsIHRvcm1lbnRhIC0tPiBncmFuaXpvIHkgbmlldmUgLS0+IG5pZXZlU3VlbG8gZGViZW4gcXVlZGFyIHJlZmxlamFkb3MgZW4gZWwgREFHLiAyLiBOaW5nw7puIGFyY28gZGViZSB1bmlyIGRpcmVjdGFtZW50ZSBsYSBuZWJsaW5hIGNvbiBlbCBncmFuaXpvIG5pIGxhIG5pZWJsYSBjb24gbGEgdG9ybWVudGEuDQoNClZ1ZWx2ZSBhIGRpYnVqYXIgbG9zIERBRyByZXN1bHRhbnRlcywgeSBhIHBhcnRpciBkZWwgQklDIG9idGVuaWRvIHBvciBjYWRhIG1vZGVsbywgZGV0ZXJtaW5hIGN1w6FsIGVzIGVsIG1lam9yIGRlIHRvZG9zIHBvbmnDqW5kb2xvcyBlbiB1bmEgdGFibGEuDQoNCkNvbXB1dGEgbGEgZnVlcnphIGRlIGxhIHJlbGFjacOzbiBlbnRyZSBub2RvcyBkZSBsb3MgZG9zIMO6bHRpbW9zIG1vZGVsb3MgcXVlIGNvbWJpbmFuIG51ZXN0cmEgZXhwZXJpZW5jaWEgY29uIGVsIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvLg0KDQpDb21lbnRhIGJyZXZlbWVudGUgbG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zIHRyYXMgY29tYmluYXIgbnVlc3RybyBjb25vY2ltaWVudG8gY29uIGVsIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvLg==